package ru.ifmo.rybakov.muclumon.msvs.proxy;

import java.rmi.RemoteException;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import org.apache.axis2.AxisFault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.rybakov.muclumon.Cluster;
import ru.ifmo.rybakov.muclumon.ClusterState;
import ru.ifmo.rybakov.muclumon.Database;
import ru.ifmo.rybakov.muclumon.InvalidConfigurationException;
import ru.ifmo.rybakov.muclumon.Revision;
import ru.ifmo.rybakov.muclumon.RevisionType;
import ru.ifmo.rybakov.muclumon.msvs.axis2.Axis2ConversionUtils;
import ru.ifmo.rybakov.muclumon.msvs.axis2.ClusterInfo;
import ru.ifmo.rybakov.muclumon.msvs.axis2.ClusterStateInfo;
import ru.ifmo.rybakov.muclumon.msvs.axis2.Code;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.ArrayOfStringE;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.AuthSoapHeader;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.AuthSoapHeaderE;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterInfo;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterInfoResponse;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterList;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterListResponse;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterStateInfo;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.GetClusterStateInfoResponse;
import ru.ifmo.rybakov.muclumon.msvs.proxy.axis2.IntegratorProxyServiceStub;

public class MSVSProxyClient {
	private static final Logger logger = LoggerFactory.getLogger(MSVSProxyClient.class);
	
	public static final String INTEGRATOR_URL = "http://194.85.163.237:8121/IntegratorProxyService/IntegratorProxyService.asmx";
	
	private AuthSoapHeaderE authSoapHeaderElement;
	
	private static final String AUTH_SOAP_LOGIN = "integrator_stat";
	private static final String AUTH_SOAP_PASSWORD = "uykjsdh9034rhdjksfkjxhcis9d983dKJH32asd";
	
	public MSVSProxyClient() {
		AuthSoapHeader authSoapHeader = new AuthSoapHeader();
		authSoapHeader.setLogin(AUTH_SOAP_LOGIN);
		authSoapHeader.setPassword(AUTH_SOAP_PASSWORD);
		
		authSoapHeaderElement = new AuthSoapHeaderE();
		authSoapHeaderElement.setAuthSoapHeader(authSoapHeader);
	}
	
	public void buildStateSnapshot(String url) {
		logger.info("Begin requesting multi-cluster state (URL={})", url);
		EntityManager em = Database.getEntityManagerFactory().createEntityManager();
		
		Revision revision = null;
		List<Cluster> clusters = null;
		try {
			Query query = em.createQuery("SELECT r FROM Revision r WHERE r.type = :type ORDER BY r.timestamp DESC");
			query.setParameter("type", RevisionType.CLUSTER_CONFIGURATION);
			query.setMaxResults(1);
			revision = (Revision) query.getSingleResult();
			
			logger.info("Building state snapshot for configuration revision #{}, {}", new Object[] { revision.getId(), revision.getTimestamp() });
			
			query = em.createQuery("SELECT c FROM Cluster c WHERE c.revision = :revision");
			query.setParameter("revision", revision);
			clusters = query.getResultList();
			
			// now create revision
			revision = new Revision();
			revision.setType(RevisionType.CLUSTER_STATE);
			EntityTransaction t = em.getTransaction();
			try {
				t.begin();
				em.persist(revision);
				t.commit();
			} finally {
				if (t.isActive()) t.rollback();
			}
		} finally {
			em.close();
		}
		
		for (Cluster c : clusters) {
			buildClusterState(url, revision, c);
		}
		logger.info("End requesting multi-cluster state");
	}
	
	private void buildClusterState(String url, Revision revision, Cluster cluster) {
		logger.debug("Retrieving cluster '{}' state", cluster.getName());
		try {
			IntegratorProxyServiceStub integrator = new IntegratorProxyServiceStub(url);
			GetClusterStateInfo request = new GetClusterStateInfo();
			request.setClusterName(cluster.getName());
			GetClusterStateInfoResponse response = integrator.getClusterStateInfo(request, authSoapHeaderElement);
			ClusterStateInfo clusterStateInfo = response.getGetClusterStateInfoResult();
			if (!Code.OperationSuccess.equals(response.getErrCode())) {
				logger.warn("Integrator reported on operation failure ({})", response.getErrCode());
			}
			if (clusterStateInfo == null) {
				logger.warn("Cluster state info was not returned by integrator for cluster '{}'", cluster.getName());
				return;
			}
			ClusterState clusterState = Axis2ConversionUtils.convert(revision, clusterStateInfo, cluster);
			EntityManager em = Database.getEntityManagerFactory().createEntityManager();
			try {
				EntityTransaction t = em.getTransaction();
				try {
					t.begin();
					em.merge(clusterState);
					t.commit();
				} finally {
					if (t.isActive()) t.rollback();
				}
			} finally {
				em.close();
			}
		} catch (AxisFault e) {
			logger.warn("Unexpected Axis2 exception", e);
		} catch (RemoteException e) {
			logger.warn("Unexpected remote I/O exception", e);
		} catch (InvalidConfigurationException e) {
			logger.warn("Invalid configuration snapshot", e);
		}
	}
	
	public void buildConfigurationSnapshot(String url) {
		logger.info("Begin requesting multi-cluster configuration (URL={})", url);
		try {
			IntegratorProxyServiceStub integrator = new IntegratorProxyServiceStub(url);
			GetClusterList request = new GetClusterList();
			GetClusterListResponse response = integrator.getClusterList(request, authSoapHeaderElement);
			ArrayOfStringE clusterArrayOfString = response.getGetClusterListResult();
			if (clusterArrayOfString == null) {
				logger.warn("ArrayOfString element for cluster array is null in getClusterList response");
				return;
			}
			
			String[] clusterNames = clusterArrayOfString.getString();
			if (clusterNames == null) {
				logger.info("Empty cluster list returned from integrator");
				clusterNames = new String[0];
			} else {
				logger.debug("{} clusters detected", clusterNames.length);
			}
			
			// create revision
			Revision revision = new Revision();
			revision.setType(RevisionType.CLUSTER_CONFIGURATION);
			
			// persist revision
			EntityManager em = Database.getEntityManagerFactory().createEntityManager();
			try {
				EntityTransaction t = em.getTransaction();
				try {
					t.begin();
					em.persist(revision);
					t.commit();
				} finally {
					if (t.isActive()) {
						t.rollback();
					}
				}
			} finally {
				em.close();
			}
			
			// now fetch every cluster information
			for (String name : clusterNames) {
				buildClusterConfiguration(url, revision, name);
			}
		} catch (AxisFault e) {
			logger.warn("Unexpected Axis2 exception", e);
		} catch (RemoteException e) {
			logger.warn("Unexpected remote I/O exception", e);
		} catch (Throwable e) {
			logger.warn("Unexpected exception", e);
		}
		logger.info("End requesting multi-cluster configuration");
	}
	
	private void buildClusterConfiguration(String url, Revision revision, String clusterName) {
		logger.debug("Retrieving cluster '{}' configuration", clusterName);
		try {
			IntegratorProxyServiceStub integrator = new IntegratorProxyServiceStub(url);
			GetClusterInfo request = new GetClusterInfo();
			request.setClusterName(clusterName);
			GetClusterInfoResponse response = integrator.getClusterInfo(request, authSoapHeaderElement);
			if (!Code.OperationSuccess.equals(response.getErrCode())) {
				logger.warn("Integrator reported on operation failure ({})", response.getErrCode());
			}
			ClusterInfo clusterInfo = response.getGetClusterInfoResult();
			if (clusterInfo == null) {
				logger.warn("Cluster info was not returned by integrator for cluster '{}'", clusterName);
				return;
			}
			
//			Query query = em.createQuery("SELECT c FROM Cluster c WHERE c.name = :name");
//			query.setParameter("name", clusterName);
//			List<Cluster> clusterList = query.getResultList();
//			if (clusterList == null) {
//				// no cluster found
//			}
			
			// now persist cluster information
			Cluster cluster = Axis2ConversionUtils.convert(revision, clusterInfo);
			EntityManager em = Database.getEntityManagerFactory().createEntityManager();
			try {
				EntityTransaction t = em.getTransaction();
				try {
					t.begin();
					em.merge(cluster);
					t.commit();
				} finally {
					if (t.isActive()) t.rollback();
				}
			} finally {
				em.close();
			}
		} catch (AxisFault e) {
			logger.warn("Unexpected Axis2 exception", e);
		} catch (RemoteException e) {
			logger.warn("Unexpected remote I/O exception", e);
		}
	}

//	public void buildTaskStateSnapshot(String url) {
//		logger.info("Begin requesting multi-cluster active tasks state (URL={})", url);
//		try {
//			IntegratorProxyServiceStub integrator = new IntegratorProxyServiceStub(url);
//			GetActiveTaskList request = new GetActiveTaskList();
//			GetActiveTaskListResponse response = integrator.getActiveTaskList(request, authSoapHeaderElement);
//			if (!Code.OperationSuccess.equals(response.getErrCode())) {
//				logger.warn("Integrator reported on operation failure ({})", response.getErrCode());
//			}
//			ArrayOfTaskInfo taskInfoArray = response.getGetActiveTaskListResult();
//			if (taskInfoArray == null) {
//				logger.warn("No task inforamation returned at all");
//				return;
//			}
//			TaskInfo[] taskInfo = taskInfoArray.getTaskInfo();
//			if (taskInfo == null) {
//				logger.info("Empty task list returned from integrator");
//				taskInfo = new TaskInfo[0];
//			}
//			
//			// create revision
//			Revision revision = new Revision();
//			revision.setType(RevisionType.TASK_STATE);
//			
//			// persist revision
//			EntityManager em = Database.getEntityManagerFactory().createEntityManager();
//			try {
//				EntityTransaction t = em.getTransaction();
//				try {
//					t.begin();
//					em.persist(revision);
//					t.commit();
//				} finally {
//					if (t.isActive()) {
//						t.rollback();
//					}
//				}
//			} finally {
//				em.close();
//			}
//			
//			for (TaskInfo info : taskInfo) {
//				updateTaskState(revision, info);
//			}
//		} catch (AxisFault e) {
//			logger.warn("Unexpected Axis2 exception", e);
//		} catch (RemoteException e) {
//			logger.warn("Unexpected remote I/O exception", e);
//		}
//		logger.info("End requesting multi-cluster active tasks state (URL={})", url);
//	}
//	
//	private void updateTaskState(Revision revision, TaskInfo info) {
//		String taskID = info.getTaskID();
//		// do not make task configuration duplicates
//		EntityManager em = Database.getEntityManagerFactory().createEntityManager();
//		try {
//			Query query = em.createQuery("SELECT t FROM Task t WHERE t.name = :name");
//			query.setParameter("name", taskID);
//			query.setMaxResults(1);
//			List<Task> tasks = query.getResultList();
//			Task task;
//			if (tasks == null || tasks.size() == 0) {
//				logger.info("No task '{}' configuration found, redirecting request to PES", taskID);
//				// persist task information
//				// TODO query PES and retrieve complete task information available
//				task = new Task();
//				task.setRevision(revision);
//				task.setName(taskID);
//				
//				if (info.getMaxExecutionTime() != 0) {
//					task.addPropertyValue(new NumericValue(Properties.TASK_STATE_MAX_TIME, info.getMaxExecutionTime()));
//				}
//				if (info.getUserName() != null) {
//					task.addPropertyValue(new StringValue(Properties.TASK_STATE_USER, info.getUserName()));
//				}
//				
//				// TODO may count of nodes required change during calculations?
//				task.addPropertyValue(new NumericValue(Properties.TASK_STATE_NODE_COUNT, info.getNumberOfNodes()));
//			} else {
//				logger.debug("Task '{}' configuration found", taskID);
//				task = tasks.get(0);
//				
//				// check if task already finished (failed for now)
//				query = em.createQuery("SELECT ts FROM TaskState ts JOIN ts.propertyValues pv WHERE ts.task = :task ORDER BY pv.id DESC");
//				query.setParameter("task", task);
//				
//				query.setMaxResults(1);
//				List<TaskState> states = query.getResultList();
//				if (states != null && states.size() > 0) {
//					TaskState ts = states.get(0);
//					query = em.createQuery("SELECT sv FROM StringValue sv WHERE sv.element = :element AND sv.property = :property AND (sv.value = 'Fail' OR sv.value = 'Finish' OR sv.value = 'Cancel' OR sv.value = 'Complete')");
//					query.setParameter("element", ts);
//					query.setParameter("property", Properties.TASK_STATE);
//					List<StringValue> sv = query.getResultList();
//					if (sv != null && sv.size() > 0) {
//						logger.debug("Task '{}' is already failed/finished/canceled/completed, stop processing state", taskID);
//						return;
//					}
//				}
//			}
//			
//			// now set or update task properties
//			if (info.getStartTime().get(Calendar.YEAR) > 1 && !task.isPropertySet(Properties.TASK_STATE_START_TIME)) {
//				task.addPropertyValue(new DateTimeValue(Properties.TASK_STATE_START_TIME, new DateTime(info.getStartTime())));
//			}
//			if (info.getFinishTime().get(Calendar.YEAR) > 1 && !task.isPropertySet(Properties.TASK_STATE_FINISH_TIME)) {
//				task.addPropertyValue(new DateTimeValue(Properties.TASK_STATE_FINISH_TIME, new DateTime(info.getFinishTime())));
//			}
//			
//			// merge task information
//			EntityTransaction t = em.getTransaction();
//			try {
//				t.begin();
//				task = em.merge(task);
//				t.commit();
//			} finally {
//				if (t.isActive()) t.rollback();
//			}
//			
//			// persist task state
//			TaskState taskState = Axis2ConversionUtils.convert(revision, info);
//			taskState.setTask(task);
//			t = em.getTransaction();
//			try {
//				t.begin();
//				em.persist(taskState);
//				t.commit();
//			} finally {
//				if (t.isActive()) t.rollback();
//			}
//		} finally {
//			em.close();
//		}
//	}

}
